﻿using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using NetMicro.Cache;
using NetMicro.Core.Extensions;

namespace NetMicro.JwtBearer
{
    public class JwtHandler : IJwtHandler
    {
        private readonly IRedisCache _redisCache;
        private readonly JWTSettingsOptions jwtSettings;
        public JwtHandler(IRedisCache redisCache, IOptions<JWTSettingsOptions> options)
        {
            _redisCache = redisCache;
            jwtSettings = options.Value;
        }

        /// <summary>
        /// 生成 Token
        /// </summary>
        /// <param name="payload"></param>
        /// <param name="expiredTime">过期时间（分钟）</param>
        /// <returns></returns>
        public string CreateToken(IDictionary<string, object> payload, long? expiredTime = null)
        {
            (IDictionary<string, object> Payload, JWTSettingsOptions JWTSettings) = CombinePayload(payload, expiredTime);
            return CreateToken(JWTSettings.IssuerSigningKey, Payload.ToJson(), JWTSettings.Algorithm);
        }



        /// <summary>
        /// 生成 Token
        /// </summary>
        /// <param name="issuerSigningKey"></param>
        /// <param name="payload"></param>
        /// <param name="algorithm"></param>
        /// <returns></returns>
        public string CreateToken(string issuerSigningKey, string payload, string algorithm = SecurityAlgorithms.HmacSha256)
        {
            SymmetricSecurityKey securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(issuerSigningKey));
            SigningCredentials credentials = new SigningCredentials(securityKey, algorithm);

            JsonWebTokenHandler tokenHandler = new JsonWebTokenHandler();
            return tokenHandler.CreateToken(payload, credentials);
        }

        /// <summary>
        /// 生成刷新 Token
        /// </summary>
        /// <param name="accessToken"></param>
        /// <param name="expiredTime">刷新 Token 有效期（分钟）</param>
        /// <returns></returns>
        public string CreateRefreshToken(string accessToken, int expiredTime = 43200)
        {
            // 分割Token
            string[] tokenParagraphs = accessToken.Split('.', StringSplitOptions.RemoveEmptyEntries);

            int s = RandomNumberGenerator.GetInt32(10, tokenParagraphs[1].Length / 2 + 2);
            int l = RandomNumberGenerator.GetInt32(3, 13);
            string g = Guid.NewGuid().ToString("N");
            Dictionary<string, object> payload = new Dictionary<string, object>
            {
                { "f",tokenParagraphs[0] },
                { "e",tokenParagraphs[2] },
                { "s",s },
                { "l",l },
                { "k",tokenParagraphs[1].Substring(s,l) },
                { "g",g }
            };
            _redisCache.Set(TokenRedisKey.RefreshTokenKey(g), "", TimeSpan.FromMinutes(expiredTime));
            return CreateToken(payload, expiredTime);
        }

        /// <summary>
        /// 通过过期Token 和 刷新Token 换取新的 Token
        /// </summary>
        /// <param name="expiredToken"></param>
        /// <param name="refreshToken"></param>
        /// <param name="expiredTime">过期时间（分钟）</param>
        /// <returns></returns>
        public string RefreshToken(string expiredToken, string refreshToken, long? expiredTime = null)
        {
            // 交换刷新Token 必须验证token有效性
            (bool _isValid, JsonWebToken _, TokenValidationResult _) = Validate(expiredToken, false);
            if (!_isValid) return default;

            // 判断刷新Token 是否过期
            (bool isValid, JsonWebToken refreshTokenObj, TokenValidationResult _) = Validate(refreshToken);
            if (!isValid) return default;

            bool keyExists = _redisCache.Exists(TokenRedisKey.RefreshTokenKey(refreshTokenObj.GetPayloadValue<string>("g")));
            if (!keyExists) return default;
            // 分割过期Token
            string[] tokenParagraphs = expiredToken.Split('.', StringSplitOptions.RemoveEmptyEntries);
            if (tokenParagraphs.Length < 3) return default;

            // 判断各个部分是否匹配
            if (!refreshTokenObj.GetPayloadValue<string>("f").Equals(tokenParagraphs[0])) return default;
            if (!refreshTokenObj.GetPayloadValue<string>("e").Equals(tokenParagraphs[2])) return default;
            if (!tokenParagraphs[1].Substring(refreshTokenObj.GetPayloadValue<int>("s"), refreshTokenObj.GetPayloadValue<int>("l")).Equals(refreshTokenObj.GetPayloadValue<string>("k"))) return default;

            // 获取过期 Token 的存储信息
            JsonWebToken oldToken = ReadJwtToken(expiredToken);
            Dictionary<string, object> payload = oldToken.Claims.Where(u => !StationaryClaimTypes.Contains(u.Type))
                                         .ToDictionary(u => u.Type, u => (object)u.Value, new MultiClaimsDictionaryComparer());
            return CreateToken(payload, expiredTime);
        }


        /// <summary>
        /// 验证 Token
        /// </summary>
        /// <param name="accessToken"></param>
        /// <param name="validateLifetime"></param>
        /// <returns></returns>
        public (bool IsValid, JsonWebToken Token, TokenValidationResult validationResult) Validate(string accessToken, bool validateLifetime = true)
        {

            if (jwtSettings == null) return (false, default, default);

            // 加密Key
            SymmetricSecurityKey key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.IssuerSigningKey));
            SigningCredentials creds = new SigningCredentials(key, jwtSettings.Algorithm);

            // 创建Token验证参数
            TokenValidationParameters tokenValidationParameters = jwtSettings.CreateTokenValidationParameters();
            tokenValidationParameters.ValidateLifetime = validateLifetime;
            if (tokenValidationParameters.IssuerSigningKey == null) tokenValidationParameters.IssuerSigningKey = creds.Key;

            // 验证 Token
            JsonWebTokenHandler tokenHandler = new JsonWebTokenHandler();
            try
            {
                TokenValidationResult tokenValidationResult = tokenHandler.ValidateToken(accessToken, tokenValidationParameters);
                if (!tokenValidationResult.IsValid) return (false, null, tokenValidationResult);

                JsonWebToken jsonWebToken = tokenValidationResult.SecurityToken as JsonWebToken;
                return (true, jsonWebToken, tokenValidationResult);
            }
            catch
            {
                return (false, default, default);
            }
        }



        /// <summary>
        /// 读取 Token，不含验证
        /// </summary>
        /// <param name="accessToken"></param>
        /// <returns></returns>
        public JsonWebToken ReadJwtToken(string accessToken)
        {
            JsonWebTokenHandler tokenHandler = new JsonWebTokenHandler();
            if (tokenHandler.CanReadToken(accessToken))
            {
                return tokenHandler.ReadJsonWebToken(accessToken);
            }

            return default;
        }





        /// <summary>
        /// 组合 Claims 负荷
        /// </summary>
        /// <param name="payload"></param>
        /// <param name="expiredTime">过期时间，单位：分钟</param>
        /// <returns></returns>
        private (IDictionary<string, object> Payload, JWTSettingsOptions JWTSettings) CombinePayload(IDictionary<string, object> payload, long? expiredTime = null)
        {

            DateTimeOffset datetimeOffset = DateTimeOffset.UtcNow;

            if (!payload.ContainsKey(JwtRegisteredClaimNames.Iat))
            {
                payload.Add(JwtRegisteredClaimNames.Iat, datetimeOffset.ToUnixTimeSeconds());
            }

            if (!payload.ContainsKey(JwtRegisteredClaimNames.Nbf))
            {
                payload.Add(JwtRegisteredClaimNames.Nbf, datetimeOffset.ToUnixTimeSeconds());
            }

            if (!payload.ContainsKey(JwtRegisteredClaimNames.Exp))
            {
                long minute = expiredTime ?? jwtSettings?.ExpiredTime ?? 20;
                payload.Add(JwtRegisteredClaimNames.Exp, DateTimeOffset.UtcNow.AddMinutes(minute).ToUnixTimeSeconds());
            }

            if (!payload.ContainsKey(JwtRegisteredClaimNames.Iss))
            {
                payload.Add(JwtRegisteredClaimNames.Iss, jwtSettings?.ValidIssuer);
            }

            if (!payload.ContainsKey(JwtRegisteredClaimNames.Aud))
            {
                payload.Add(JwtRegisteredClaimNames.Aud, jwtSettings?.ValidAudience);
            }
            if (!payload.ContainsKey(JwtRegisteredClaimNames.Aud))
            {
                payload.Add(JwtRegisteredClaimNames.Aud, jwtSettings?.ValidAudience);
            }
            return (payload, jwtSettings);
        }





        /// <summary>
        /// 固定的 Claim 类型
        /// </summary>
        private static readonly string[] StationaryClaimTypes = new[] { JwtRegisteredClaimNames.Iat, JwtRegisteredClaimNames.Nbf, JwtRegisteredClaimNames.Exp, JwtRegisteredClaimNames.Iss, JwtRegisteredClaimNames.Aud };
    }
}